﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Reflection;
using Utilities.IO;
using Utilities.Conversion;

namespace Communication.Packets
{
    /// <summary>
    /// Defines the MU Protocol's PacketType.
    /// </summary>
    public enum PacketType
    {
        C1 = 0xC1,
        C2 = 0xC2,
        C3 = 0xC3,
        C4 = 0xC4,
    };
    public class UnknownPacketTypeError : Exception
    {
    }

    public class InvalidPacketFormatError : Exception
    {
    }

    public class InvalidPacketIDError : Exception
    {
    }

    /// <summary>
    /// Represents MU Protocol Packet.
    /// </summary>
    public abstract class Packet : ISerializeable
    {
        /// <summary>
        /// Abstract static field which defines the ID of the packet.
        /// </summary>
        public static int ID = -1;

        #region Ctor
        
        /// <summary>
        /// C-tor
        /// </summary>
        /// <param name="type">The type of the packet</param>
        /// <param name="id">The id of the packet</param>
        public Packet(PacketType type, int id) 
        {
            this.m_id = id;
            this.m_type = type;
        }

        #endregion

        #region Deserialization & Serialization
        /// <summary>
        /// Construct the packet by deserializing the buffer.
        /// </summary>
        /// <param name="buffer">The buffer to deserialize</param>
        public Packet(BufferedStream buffer)
        {
            EndianBinaryReader reader = new EndianBinaryReader(EndianBitConverter.Big, buffer);
            this.m_type = (PacketType)reader.ReadByte();

            // Check size
            if (this.getPacketSize(reader) != buffer.Length)
            {
                throw new InvalidPacketFormatError();
            }

            this.m_id = reader.ReadByte();
        }

        /// <summary>
        /// Serializes the packet to a given buffer
        /// </summary>
        /// <param name="buffer">The buffer which stores the packet's serialization.</param>
        public virtual void Serialize(BufferedStream buffer)
        {
            // store the current buffer data
            // TODO: This section is very ugly. need to find elegant solution.
            // Maybe configure the writer to only append and not overwrite.
            byte[] temp_buffer = new byte[buffer.Length];
            buffer.Seek(0, SeekOrigin.Begin);
            buffer.Read(temp_buffer, 0, temp_buffer.Length);
            buffer.Seek(0, SeekOrigin.Begin);

            EndianBinaryWriter writer = new EndianBinaryWriter(EndianBitConverter.Big, buffer);
            writer.Write((byte)this.m_type);
            switch (this.m_type)
            {
                case PacketType.C1:
                case PacketType.C3:
                    writer.Write((byte)(temp_buffer.Length + 3));
                    break;
                case PacketType.C2:
                case PacketType.C4:
                    writer.Write((short)(temp_buffer.Length + 4));
                    break;
                default:
                    throw new InvalidPacketFormatError();
            }
            writer.Write((byte)this.m_id);

            // add the old buffer data
            writer.Write(temp_buffer, 0, temp_buffer.Length);
        }

        /// <summary>
        /// Gets the packet's size by its type (C1/C3 = byte, C2/C4 = short)
        /// </summary>
        /// <param name="reader">Buffer to read from</param>
        /// <returns>Packet's size.</returns>
        private int getPacketSize(EndianBinaryReader reader)
        {
            switch (this.m_type)
            {
                case PacketType.C1:
                case PacketType.C3:
                    return (int)reader.ReadByte();
                case PacketType.C2:
                case PacketType.C4:
                    return (int)reader.ReadInt16();
                default:
                    throw new InvalidPacketFormatError();
            }
        }

        #endregion

        #region Private

        #endregion

        #region Fields
        protected int m_id;
        protected PacketType m_type;
        #endregion
    }
}
